using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using System.Collections;
using NovodexWrapper;
using System.Threading;
using System.Runtime.InteropServices;

namespace marbletrack
{
    /// <summary>
    /// Marble Track Created for Comp134-08B
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        #region//variables
        Debug debug;

        static GraphicsDeviceManager graphics;
        static public GraphicsDeviceManager Graphics { get { return graphics; } }
        static private SpriteBatch spriteBatch;
        static public SpriteBatch SpriteBatch { get { return spriteBatch; } }

         
        // Physics
        static private Physics physics;
        static public Physics GetPhysics
        {
            get { return physics; }
        }

        // Content manager variables
        private static ContentManager mManager;
        public static ContentManager contentloader { get { return mManager; } }

        #region//things to do with the mouse and ray casting
        NxRay nxRay = new NxRay();
        NxRaycastHit rayCastHit = new NxRaycastHit();
        static private MouseState mouseStatePrevious;
        static public bool mouseClickedLeft
        {
            get
            {
                if (mouseStatePrevious.LeftButton == ButtonState.Released
                     && Mouse.GetState().LeftButton == ButtonState.Pressed)
                { return true; }
                else
                { return false; }
            }
        }
        static public bool mouseClickedRight
        {
            get
            {
                if (mouseStatePrevious.RightButton == ButtonState.Released
                    && Mouse.GetState().RightButton == ButtonState.Pressed)
                { return true; }
                else
                { return false; }
            }
        }
        #endregion

        //timer for the mouse down (in Frames)
        //private static int mouseDownTimer;
        //public static int mouseDownTime { get { return mouseDownTimer; } }

        //The control Disc
        CameraDisc cameraDisc;

        //the menu
        Menu menu;
        //the splash screen
        static public Splash sp = new Splash();

        #region//constants of sizes for things
        //List for all buttons at the moment
        public static List<Buttons> buttons;
        const int BUTTONHEIGHT = 75;
        const int BUTTONWIDTH = 75;
        const int NUMOFBUT = 1;
        const int ARROWBUTTONSPACING = 10;
        int BUTTONOFFSET;
        //dimensions of grid
        public const int NUMBEROFGRIDS = 20;
        public const float TOTALGRIDSIZE = 4000;
        public const float SQUARESIZE = TOTALGRIDSIZE / NUMBEROFGRIDS;
        public const float GRIDBOUNDRY = TOTALGRIDSIZE / 2;
        public const float HALFSQUARESIZE = SQUARESIZE / 2;
        public const float SCALE = 1f;
        #endregion

        //floor
        public static Floor floor;

        #region//lists
        //list to store track pieces
        public static ArrayList modelList = new ArrayList();
        //List to store supports
        public static ArrayList supportList = new ArrayList();
        //list of selection options
        public static ArrayList selectionList = new ArrayList();
        //ships to pick up camera
        public static List<Ship> shipList;
        //list of environment objects
        public static List<Obstacle> obstacleList;
        //list to store balls
        public static List<Ball> ballList;
        //list of textures so the particles can have a random colour
        public static List<Texture2D> textureList;
        //list of balls for explosion/fireworks effect
        public static List<Ball> ParticleList;
        //list of blocks that will fall over when hit
        public static List<PhysicsBlock> BlockList;
        #endregion

        // The aspect ratio determines how to scale 3d to 2d projection.
        float aspectRatio;

        #region//things to do with the camera
        //for camera view matrix
        static public Vector3 cameraStartingPosition = new Vector3(GRIDBOUNDRY * 1.5f, TOTALGRIDSIZE * 2f, GRIDBOUNDRY * 1.5f);
        static public Vector3 campos = cameraStartingPosition;
        static public Vector3 nextCamPos = campos;
        static public Vector3 camlook = StartingPosition;
        static public Vector3 nextCamLook = StartingPosition;
        static public Matrix view;
        //static public bool cameraOnRamp = true;
        static public bool cameraIsBall = false;
        static public Ball cameraBall;
        static public bool toggleCameraPosition = false;
        static public float camposModifier = 3000f;
        static public bool camLookModifier = false;
        static public Vector2 camangle = new Vector2(0,90);

        //for fly through camera
        FlyThroughCamera flyCam;
        #endregion

        //for projection matrix
        static public Matrix projectionMatrix;
        const float FIELDOFVIEWWIDTH = TOTALGRIDSIZE * 2;
        const float FIELDOFVIEWHEIGHT = TOTALGRIDSIZE * 2;

        #region//things to do with menu. Selection pieces and buttons and certain track pieces that are always in the game
        //temporary places to store certain track pieces
        public static TrackPiece selectedTrackPiece;
        public static TrackPiece mouseSelectedPiece;
        public static StraightPiece selectedStraightPiece;
        public static LeftCurvePiece selectedLeftCurvePiece;
        public static RightCurvePiece selectedRightCurvePiece;
        public static DownSlopePiece selectedDownSlopePiece;
        public static UpSlopePiece selectedUpSlopePiece;
        //public static TrackPiece removedPiece;

        public static TrackPiece lastTrackPiece;
        static public TrackPiece LastTrackPiece
        {
            get
            {
                lastTrackPiece = StartingPiece;
                //searching for end of current track or first gap
                while (lastTrackPiece.Next_ != null)
                { lastTrackPiece = lastTrackPiece.Next_; }
                return lastTrackPiece;
            }
        }

        public static EndOfTrack endPiece;
        static public TrackPiece StartingPiece;
        static public Vector3 StartingPosition;

        //button refereces
        public static Buttons straightModelButton;
        public static Buttons leftModelButton;
        public static Buttons rightModelButton;
        public static Buttons upModelButton;
        public static Buttons downModelButton;
        public static Buttons undoButton;
        #endregion

        #region//flags
        public static bool CheckButtons = false;
        public static bool trackComplete = false;//true when track is at end piece
        public static bool levelComplete = false;//true when track complete and ball has reached end piece
        public static bool endOfLevelInitialized = false;//set so that the initialisation of the end of level animations only occur once
        public static bool FlagTrackNotCorrect = false; //set when the ball has fallen off a complete track 3X
        //public static bool select = false;//not used
        bool toggle = false;//toggle is to draw the selection piece every second frame
        bool hovering = false;//determines if the mouse is over a button
        private static int frameCount = 0;//end of level animation frame counter
        private int setButtonCheck = 0;
        #endregion

        private int NumberOfTimesBallHitsGroundWhenTrackComplete = 0;

        #region//models
        static public PointingArrow pointingArrow;
        static private ModelClass _StraightModel;
        static public ModelClass StraightModel
        {
            get
            {
                if (_StraightModel == null)
                    _StraightModel = new ModelClass("Models\\straight");

                return _StraightModel;
            }
        }
        static private ModelClass _CurveModel;
        static public ModelClass CurveModel
        {
            get
            {
                if (_CurveModel == null)
                {
                    _CurveModel = new ModelClass("Models\\curve");
                }

                return _CurveModel;
            }
        }
        static private ModelClass _CurveLeftModel;
        static public ModelClass CurveLeftModel
        {
            get
            {
                if (_CurveLeftModel == null)
                {
                    _CurveLeftModel = new ModelClass("Models\\curveLeft");
                }

                return _CurveLeftModel;
            }
        }
        static private ModelClass _CurveRightModel;
        static public ModelClass CurveRightModel
        {
            get
            {
                if (_CurveRightModel == null)
                {
                    _CurveRightModel = new ModelClass("Models\\curveRight");
                }

                return _CurveRightModel;
            }
        }
        static private ModelClass _SlopeModel;
        static public ModelClass SlopeModel
        {
            get
            {
                if (_SlopeModel == null)
                {
                    _SlopeModel = new ModelClass("Models\\slope");
                }

                return _SlopeModel;
            }
        }
        static private ModelClass _UpSlopeModel;
        static public ModelClass UpSlopeModel
        {
            get
            {
                if (_UpSlopeModel == null)
                {
                    _UpSlopeModel = new ModelClass("Models\\upSlope");
                }

                return _UpSlopeModel;
            }
        }
        static private ModelClass _BallModel;
        static public ModelClass BallModel
        {
            get
            {
                if (_BallModel == null)
                    _BallModel = new ModelClass("Models\\ball");

                return _BallModel;
            }
        }
        static private ModelClass _ShipModel;
        static public ModelClass ShipModel
        {
            get
            {
                if (_ShipModel == null)
                {
                    _ShipModel = new ModelClass("Models\\p1_wedge");
                }

                return _ShipModel;
            }
        }
        static private ModelClass _endPiece;
        static public ModelClass EndPiece
        {
            get
            {
                if (_endPiece == null)
                    _endPiece = new ModelClass("Models\\yellowpyramid");

                return _endPiece;
            }
        }
        static private ModelClass _greenPyramid;
        static public ModelClass GreenPyramid
        {
            get
            {
                if (_greenPyramid == null)
                    _greenPyramid = new ModelClass("Models\\greenpyramid");

                return _greenPyramid;
            }
        }
        static private ModelClass _greenCube;
        static public ModelClass GreeenCube
        {
            get
            {
                if (_greenCube == null)
                    _greenCube = new ModelClass("Models\\greencube");

                return _greenCube;
            }
        }        
        static private ModelClass _bluePyramid;
        static public ModelClass BluePyramid
        {
            get
            {
                if (_bluePyramid == null)
                    _bluePyramid = new ModelClass("Models\\bluepyramid");

                return _bluePyramid;
            }
        }
        static private ModelClass _blueCube;
        static public ModelClass BlueCube
        {
            get
            {
                if (_blueCube == null)
                    _blueCube = new ModelClass("Models\\bluecube");

                return _blueCube;
            }
        }        static private ModelClass _redPyramid;
        static public ModelClass RedPyramid
        {
            get
            {
                if (_redPyramid == null)
                    _redPyramid = new ModelClass("Models\\redpyramid");

                return _redPyramid;
            }
        }
        static private ModelClass _redCube;
        static public ModelClass RedCube
        {
            get
            {
                if (_redCube == null)
                    _redCube = new ModelClass("Models\\redcube");

                return _redCube;
            }
        }
        #endregion

        #region//textures 
        //static public Texture2D woodTextureOne;
        //static public Texture2D cobbleStone;
        static public Texture2D supportTexture;
        static public Texture2D ballTexture0;
        static public Texture2D ballTexture1;
        static public Texture2D ballTexture2;
        static public Texture2D ballTexture3;
        static public Texture2D ballTexture4;
        static public Texture2D mouseCurser;
        static public Texture2D bluewhite;
        static public Texture2D floorTexture;
        //static public Texture2D greenwhite;
        //static public Texture2D blue;
        //static public Texture2D fractal;
        static public Texture2D metal0;
        static public Texture2D metal1;
        //static public Texture2D metal3;
        //static public Texture2D metal4;
        //static public Texture2D tut4;
        //static public Texture2D green_rivets;
        //static public Texture2D redwhite;
        //static public Texture2D colourful;
        #endregion

        #region//basic effect
        static public BasicEffect basicEffect;
        static private BasicEffect DrawEffects(GraphicsDevice graphics, Matrix view, Matrix projectionMatrix)
        {
            basicEffect = new BasicEffect(graphics, null);
            basicEffect.TextureEnabled = true;
            basicEffect.Texture = metal0;
            basicEffect.LightingEnabled = true;
            basicEffect.EnableDefaultLighting();
            //basicEffect.FogEnabled = true;
            //basicEffect.FogColor = Color.White.ToVector3();
            //basicEffect.FogStart = 18000f;
            //basicEffect.FogEnd = 20000f;
            basicEffect.AmbientLightColor = Color.White.ToVector3();
            basicEffect.World = Matrix.Identity;
            basicEffect.View = view;
            basicEffect.Projection = projectionMatrix;
            return basicEffect;
        }
        #endregion

        private static Random random = new Random();
        public static Random Ran
        {
            get
            {
                return random;
            }
        }

        //static public bool isMouseOutOfGrid = false;
        //static public Vector3 StartingPosition = new Vector3(0,0,0);
        #endregion

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferHeight = 768;
            graphics.PreferredBackBufferWidth = 1024;
            
            Content.RootDirectory = "Content";
        }

        public void DoSplash()
        {
            Splash sp = new Splash();
            sp.ShowDialog();
        }

        protected override void Initialize()
        {
            sp.Show();

            Audio.initialize();
            mManager = Content;
            //grid = new Grid();
            //mousesSquare = new MouseSquare();
            //piecesquare = new MouseSquare();
            //piecesquare.squareColour = new Vector3(0, 191, 255);
            //mousesquare.hieght = 0F;
        
            //textures for models
            //woodTextureOne = Content.Load<Texture2D>("Textures\\woodsquare4");
            //cobbleStone = Content.Load<Texture2D>("Textures\\cobblestone1");
            supportTexture = Content.Load<Texture2D>("Textures\\supportTexture");
            mouseCurser = Content.Load<Texture2D>("Textures\\MouseCurser");
            //greenwhite = Content.Load<Texture2D>("Textures\\greenwhite");
            bluewhite = Content.Load<Texture2D>("Textures\\bluewhite");
            floorTexture = Content.Load<Texture2D>("Textures\\floorTexture");
            //blue = Content.Load<Texture2D>("Textures\\blue");
            ballTexture0 = Content.Load<Texture2D>("Textures\\ballTexture0");
            ballTexture1 = Content.Load<Texture2D>("Textures\\ballTexture1");
            ballTexture2 = Content.Load<Texture2D>("Textures\\ballTexture2");
            ballTexture3 = Content.Load<Texture2D>("Textures\\ballTexture3");
            ballTexture4 = Content.Load<Texture2D>("Textures\\ballTexture4");
            metal0 = Content.Load<Texture2D>("Textures\\metala_0");
            metal1 = Content.Load<Texture2D>("Textures\\metala_1");
            //metal3 = Content.Load<Texture2D>("Textures\\metala_3");
            //metal4 = Content.Load<Texture2D>("Textures\\metala_4");
            //tut4 = Content.Load<Texture2D>("Textures\\tut4");
            //green_rivets = Content.Load<Texture2D>("Textures\\green_rivets1");
            //redwhite = Content.Load<Texture2D>("Textures\\redwhitestripe");
            //colourful = Content.Load<Texture2D>("Textures\\colourful");

            IsMouseVisible = false;

            debug = new Debug(view, projectionMatrix);

            //Initialize physics and physics contact reports
            physics = new Physics();

            //Initialize view matrix
            view = Matrix.CreateLookAt(campos, camlook, Vector3.Up);

            //Initialize projection matrix
            aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
            (float)graphics.GraphicsDevice.Viewport.Height;
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(70f), aspectRatio,
               1f, 40000f);

            //projectionMatrix = Matrix.CreateOrthographic(TOTALGRIDSIZE*3,TOTALGRIDSIZE*2,
            //   0f, 40000f);

            //initialize the fly through camera
            flyCam = new FlyThroughCamera();
            graphics.ToggleFullScreen();
            //graphics.GraphicsDevice.RenderState.FogEnable = true;
            //graphics.GraphicsDevice.RenderState.FogColor = Color.White;
            //graphics.GraphicsDevice.RenderState.FogEnd = 20000f;
            //graphics.GraphicsDevice.RenderState.FogStart = 18000f;
            //graphics.GraphicsDevice.RenderState.FogTableMode = FogMode.Linear;
            //graphics.GraphicsDevice.RenderState.FogDensity = 0.1f;


            cameraDisc = new CameraDisc();

            sp.Close();
            base.Initialize();
        }

        //Add a particle to the ParticleList and set its actors userData field
        static public void AddParticleToList()
        {
            Ball ball = new Ball();
            ParticleList.Add(ball);

            //sets the user data field on the ball actor so that the ball can be identified in contact reports
            unsafe
            {
                BallData ballData = new BallData(ParticleList.Count - 1, BallData.BallType.Particle);
                ball.Actor.UserData = Marshal.AllocHGlobal(sizeof(BallData));
                Marshal.StructureToPtr(ballData, ball.Actor.UserData, true);
            }
        }

        //Add a ball to the ballList and set its actors userData field
        static public void AddBallToList()
        {
            if (ballList.Count <= 10)
            {
                Ball ball = new Ball();
                ballList.Add(ball);

                //sets the user data field on the ball actor so that the ball can be identified in contact reports
                unsafe
                {
                    BallData ballData = new BallData(ballList.Count - 1, BallData.BallType.Ball);
                    ball.Actor.UserData = Marshal.AllocHGlobal(sizeof(BallData));
                    Marshal.StructureToPtr(ballData, ball.Actor.UserData, true);
                }

                if (ballList.Count % 4 == 0)
                {
                    shipList.Add(new Ship());
                }
            }
        }

        //Overload for the camera ball, so that its actors userData field is also set
        static public void AddBallToList(Ball ball)
        {
            ballList.Add(ball);

            //sets the user data field on the ball actor so that the ball can be identified in contact reports
            unsafe
            {
                BallData ballData = new BallData(ballList.Count - 1, BallData.BallType.Ball);
                ball.Actor.UserData = Marshal.AllocHGlobal(sizeof(BallData));
                Marshal.StructureToPtr(ballData, ball.Actor.UserData, true);
            }

            if (ballList.Count % 4 == 0)
            {
                shipList.Add(new Ship());
            }
        }

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            //IsMouseVisible = true;
            //
            CreateInterface();
            menu.saver.SynchroniseLoad();
            // Load the ground physics
            physics.CreateGround();

            basicEffect = DrawEffects(graphics.GraphicsDevice, view, projectionMatrix);
//            texturedEffect = DrawStructureEffects(graphics.GraphicsDevice, view, projectionMatrix);
            InitializeStartingPieces(menu.saver);
            menu.saver.Loaded = false;
            
        }

        static void InitializeStartingPieces(Saver saver)
        {
            shipList = new List<Ship>();
            obstacleList = new List<Obstacle>();
            ballList = new List<Ball>();
            modelList = new ArrayList();
            supportList = new ArrayList();
            selectionList = new ArrayList();
            ParticleList = new List<Ball>();
            BlockList = new List<PhysicsBlock>();

            //setting up starting piece
            StartingPiece = new DownSlopePiece();
            //SnapMouseToGrid(ref StartingPiece.ModelPosition);
            //SnapMouseToGrid(ref StartingPosition);
            //StartingPiece.modelPosition_ = StartingPosition;

            if (saver.Loaded)
            {
                saver.LoadStartingPosition(ref StartingPosition);
            }
            else
            {
                StartingPosition = new Vector3(
                    (float)(random.Next(-10000, 10000)),
                    (float)(random.Next(500, 5000)),
                    (float)(random.Next(-10000, 10000)));
            }
            StartingPiece.ModelPosition = StartingPosition;
            StartingPiece.modelRotation_ = StartingPiece.directionFromNorth;
            StartingPiece.SetEntryExitPoints();
            StartingPiece.ApplyTransformations();
            StartingPiece.Prev_ = StartingPiece;
            StartingPiece.support = new Support(StartingPiece);
            modelList.Add(StartingPiece);

            supportList.Add(StartingPiece.support);

            selectionList.Add(selectedStraightPiece = new StraightPiece());
            selectionList.Add(selectedLeftCurvePiece = new LeftCurvePiece());
            selectionList.Add(selectedRightCurvePiece = new RightCurvePiece());
            selectionList.Add(selectedDownSlopePiece = new DownSlopePiece());
            selectionList.Add(selectedUpSlopePiece = new UpSlopePiece());
            selectionList.Add(selectedTrackPiece = selectedStraightPiece);
            foreach (TrackPiece selection in selectionList)
            {
                selection.ModelPosition = new Vector3(0, -10000, 0);
                selection.Update();
            }

            ButtonCheck();
            //removedPiece = null;
            lastTrackPiece = StartingPiece;
            //some starting balls
            cameraBall = new Ball();
            AddBallToList(cameraBall);
            for (int i = 0; i < 50; i++)
            {
                AddParticleToList();
            }

            //test block
            for (float i = 600; i < 600 * 10; i += 600)
            {
                for (float j = -600 * 3; j < 600 * 2; j += 600)
                {
                    BlockList.Add(new PhysicsBlock(Matrix.CreateTranslation(j, i, 0), (float)random.NextDouble() + 0.5f));
                }
            }

            //texture list is to create random colours
            textureList = new List<Texture2D>();
            textureList.Add(ballTexture0);
            textureList.Add(ballTexture1);
            textureList.Add(ballTexture2);
            textureList.Add(ballTexture3);
            textureList.Add(ballTexture4);

            //floor
            floor = new Floor();

            //ship to pick up camera ball
            shipList.Add(new Ship());
            //obstacles
            if (saver.Loaded)
            {
                try
                {
                    saver.LoadObsticals(ref obstacleList);
                    endPiece = (EndOfTrack)obstacleList[0];
                }
                catch
                {
                    CreateRandomObstacleList();
                }
            }
            else
            {
                CreateRandomObstacleList();
            }
            trackComplete = false;
            levelComplete = false;
            //pointing arrow must be updated after endpiece has been instanciated
            pointingArrow = new PointingArrow();
            pointingArrow.Update();

            camLookModifier = false;

            campos = Vector3.Zero;
            cameraIsBall = false;
            levelComplete = false;
            trackComplete = false;
            //FlagClearBallList = false;
            if (saver.Loaded)
            {
                saver.LoadTrack();

            }
            CreateParticles(new Vector3(0, 5000, 0), new Vector3(10000, -1000, 1000));
            foreach (Ball particle in ParticleList)
            {
                particle.Actor.FlagDisableGravity = false;
            }
        }

        static public void CreateRandomObstacleList()
        {
            endPiece = new EndOfTrack();
            obstacleList.Add(endPiece);
            //change when these when level loads
            for (int i = 0; i < 2; i++)
            {
                obstacleList.Add(new TreeObject(new Vector3((float)(Ran.Next(-10, 10)) * 1000, 0, (float)(Ran.Next(-10, 10)) * 1000), RedPyramid));
                obstacleList.Add(new TreeObject(new Vector3((float)(Ran.Next(-10, 10)) * 1000, 0, (float)(Ran.Next(-10, 10)) * 1000), GreenPyramid));
                obstacleList.Add(new TreeObject(new Vector3((float)(Ran.Next(-10, 10)) * 1000, 0, (float)(Ran.Next(-10, 10)) * 1000), BluePyramid));
                obstacleList.Add(new TreasureChest(new Vector3((float)(Ran.Next(-10, 10)) * 1000, 120, (float)(Ran.Next(-10, 10)) * 1000)));
            }            
        }
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        static public bool SnapMouseToGrid(ref Vector3 mousePosition)
        {
            //snaps any vector3 to a grid
            bool returnValue = false;
            mousePosition.X /= HALFSQUARESIZE;
            mousePosition.X = (float)(Math.Floor(mousePosition.X));
            mousePosition.X *= HALFSQUARESIZE;
            if (mousePosition.X >= GRIDBOUNDRY)
            {
                returnValue = true;
                mousePosition.X = GRIDBOUNDRY - HALFSQUARESIZE;
            }
            if (mousePosition.X <= -GRIDBOUNDRY)
            {
                returnValue = true;
                mousePosition.X = -GRIDBOUNDRY + HALFSQUARESIZE;
            }

            mousePosition.Z /= HALFSQUARESIZE;
            mousePosition.Z = (float)(Math.Floor(mousePosition.Z));
            mousePosition.Z *= HALFSQUARESIZE;
            if (mousePosition.Z >= GRIDBOUNDRY)
            {
                returnValue = true;
                mousePosition.Z = GRIDBOUNDRY - HALFSQUARESIZE;
            }
            if (mousePosition.Z <= -GRIDBOUNDRY)
            {
                returnValue = true;
                mousePosition.Z = -GRIDBOUNDRY + HALFSQUARESIZE;
            }
            return returnValue;
        }

        protected override void Update(GameTime gameTime)
        {
            if (this.IsActive)
            {
                //KeyboardState keyboardState = Keyboard.GetState();

                #region ScreenShot
                /*
                if (keyboardState.IsKeyDown(Keys.Q))
                {
                    graphics.IsFullScreen = true;
                }
                else if (keyboardState.IsKeyDown(Keys.W))
                {
                    graphics.IsFullScreen = false;
                }
                else if (keyboardState.IsKeyDown(Keys.S))
                {
                    buttons = new List<Buttons>();
                    menu.submenus = new List<Buttons>();
                    menu.menuDepth = 0;
                }
                else if (keyboardState.IsKeyDown(Keys.A))
                {
                    CreateInterface();
                }
                */
                #endregion

                //update physics
                physics.tickPhysics();

                //particles list is explosion of balls
                {
                    foreach (Ball ball in ParticleList)
                    {

                        ball.Update();
                    }
                    frameCount++;
                }

                if (!levelComplete)
                {
                    NormalUpdateProcedure();
                }
                else
                {
                    EndOfLevel();
                }
                
                campos -= (campos - nextCamPos) / 10;
               
                camlook -= (camlook - nextCamLook) / 25;

                debug.Update();

                 base.Update(gameTime);
            }
        }

        private void NormalUpdateProcedure()
        {
            //this method is what normally updates during the game but is not need at the end
            //of level animations

            MouseState mouseStateCurrent = Mouse.GetState();
            
            //Update the 3D sound positions and audio values
            Audio.listener.Position = nextCamPos;
            Audio.update();

            //cast a ray to detect objects
            if (mouseClickedLeft)
            {
                //ray cast the mouse
                Vector3 cursorRayStart = graphics.GraphicsDevice.Viewport.Unproject(new Vector3(mouseStateCurrent.X, mouseStateCurrent.Y, 0),
                       projectionMatrix, view, Matrix.Identity);
                Vector3 cursorRayEnd = graphics.GraphicsDevice.Viewport.Unproject(new Vector3(mouseStateCurrent.X, mouseStateCurrent.Y, 0.1f),
                    projectionMatrix, view, Matrix.Identity);
                Vector3 direction = cursorRayEnd - cursorRayStart;
                //direction.Normalize();
                nxRay.orig = MathConversion.ToNxVec3(campos);
                nxRay.dir = MathConversion.ToNxVec3(direction);
                //normalizing the direction vector after converting to NxVec seems to make it work better
                nxRay.dir.normalize();
                physics.PhysicsScene.raycastClosestShape(nxRay, NxShapesType.NX_ALL_SHAPES, out rayCastHit);
                Vector3 hitPosition = MathConversion.ToVector3(rayCastHit.worldImpact);

                foreach (Obstacle obstacle in obstacleList)
                {
                    if (Vector3.Distance(hitPosition, obstacle.modelPosition_) < obstacle.mouseClickTolerance)
                    {
                        obstacle.active = true;
                    }
                }
            }
            foreach (Obstacle obstacle in obstacleList)
            {
                obstacle.Update();
            }

            if (trackComplete)
            {
                cameraIsBall = true;
                if (NumberOfTimesBallHitsGroundWhenTrackComplete > 3)
                {
                    // track is complete but ball is not getting to end
                    cameraIsBall = false;
                    FlagTrackNotCorrect = true;
                    trackComplete = false;
                    NumberOfTimesBallHitsGroundWhenTrackComplete = 0;
                    Buttons.Undo();
                    //FlagClearBallList = false;
                }
            }

            if (Vector3.Distance(cameraBall.Position, endPiece.modelPosition_) < SQUARESIZE *1f && trackComplete)// && !endOfLevelInitialized)
            {
                //End of level
                levelComplete = true;
            }

            //update ball position with calculated location from physics engine
            foreach (Ball ball in ballList)
            {
                ball.Update();
                Audio.i++;
                Audio.emitter_M.Position = ball.Position;
                if (Vector3.Distance(ball.Position, StartingPiece.ModelPosition) > GRIDBOUNDRY * 50)
                {
                    ball.Position = Vector3.UnitY * -1000;//if ball gets to far away this sets it closer
                }
                else if (Vector3.Distance(ball.Velocity, Vector3.Zero) < 50f || ball.Position.Y < 100f)
                {
                    
                    if (ball.ship_ == null && Vector3.Distance(ball.Position, StartingPosition) > SQUARESIZE * 2)
                    {
                        if (trackComplete && ball == cameraBall)
                        {
                            NumberOfTimesBallHitsGroundWhenTrackComplete++;//counts number of times ball stops when track complete
                        }
                        //if a ball stops send a ship to pick it up
                        foreach (Ship ship in shipList)
                        {
                            if (ship.ball_ == null
                                && !ship.FlagDropBall
                                && ball.Position != ship.startingPosition
                                && ball.ship_ == null)
                            {
                                ship.ball_ = ball;
                                ball.ship_ = ship;
                                break;
                            }
                        }
                    }
                }
                
            }

            //update ship position from ball position
            foreach (Ship ship in shipList)
            {
                ship.Update();
            }

            //update physics engine with tracks position
            foreach (TrackPiece piece in modelList)
            {
                piece.Update();
            }

            //update the blocks positions from the physics engine
            foreach (PhysicsBlock block in BlockList)
            {
                block.Update();
            }

            if (cameraIsBall == true)
            {
                //this is where the camera follows the ball
                camLookModifier = false;
                if (Vector3.Distance(cameraBall.Velocity, Vector3.Zero) < 50f || cameraBall.Position.Y < 100f)
                {
                    nextCamLook = shipList[0].modelPosition_;
                }
                else
                {
                    nextCamLook = cameraBall.Position + cameraBall.Velocity * 2f;
                }
                nextCamPos = (cameraBall.Position - (cameraBall.Velocity / 8f) + Vector3.UnitY * HALFSQUARESIZE);
            }
            else
            {
                //this is when the camera is not following the ball
                //the camposModisfier is controlled by the scroll wheel to zoom, and the camangle controlled by the camera disc
                cameraDisc.Update();

                //puts a limit on camera height
                if (camangle.X > 90)
                {
                    camangle.X = 90;
                }
                else if (camangle.X < -90)
                {
                    camangle.X = -90;
                }

                nextCamLook = lastTrackPiece.exitPoint_;
                nextCamPos.X = (float)(Math.Sin(MathHelper.ToRadians(camangle.Y))) * camposModifier;
                nextCamPos.Z = (float)(Math.Cos(MathHelper.ToRadians(camangle.Y))) * camposModifier;
                nextCamPos.Y = (float)(Math.Sin(MathHelper.ToRadians(camangle.X))) * camposModifier;
                nextCamPos += lastTrackPiece.ModelPosition;
            }

            menu.Update();

            if (!levelComplete && CheckButtons)
            {
                //checks if buttons need to be removed from display
                ButtonCheck();
                if (setButtonCheck >= 2)
                {
                    CheckButtons = false;
                    setButtonCheck = 0;
                }
                setButtonCheck++;
            }
            if (!trackComplete)
            {
                hovering = false;
                //sets the flashing selection piece when mouse over
                foreach (Buttons button in buttons)
                {
                    if (button.active)
                    {
                        if (button.Update() == true)
                        {
                            hovering = true;
                        }
                    }
                }
                if (!hovering)
                {
                    selectedTrackPiece = null;
                }
            }
            mouseStatePrevious = mouseStateCurrent;
        }

        //check for collisions between supports and track pieces and removes any supports that are colliding
        public static void CheckForSupportCollisions()
        {
            for (int i = 0; i < supportList.Count; i++)
            {
                Support support = (Support)(supportList[i]);
                if (support.Overlaps())
                {
                    RemoveSupport(support);
                    i--;
                }
            }
        }

        protected override void Draw(GameTime gameTime)
        {
            view = Matrix.CreateLookAt(campos, camlook, Vector3.Up);
            
            graphics.GraphicsDevice.Clear(Color.White);
            
            //Set texture addressing mode to wrap (enables tiling of textures)
            graphics.GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
            graphics.GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;

            #region Debug Code
            //debug.Position = Matrix.CreateTranslation(-Game1.SQUARESIZE * 0.05f, Game1.SQUARESIZE * 0.097f, 0);
            //debug.Draw(BallModel, view, projectionMatrix);

            //debug.Position = Matrix.CreateTranslation(-Game1.SQUARESIZE * 1.5f, -Game1.HALFSQUARESIZE, 0);
            //debug.Draw(StraightModel, view, projectionMatrix);

            //debug.Position = Matrix.CreateTranslation(-Game1.SQUARESIZE * 0.617f, -Game1.SQUARESIZE * 0.686f, Game1.SQUARESIZE * 0.05f);
            //debug.Position = Matrix.CreateTranslation(Game1.SQUARESIZE, -Game1.HALFSQUARESIZE, -Game1.HALFSQUARESIZE)
            //* Matrix.CreateRotationY(MathHelper.ToRadians(180)) * Matrix.CreateScale(1, 1, -1);
            //debug.Draw(_treasureChest, view, projectionMatrix);
            #endregion

            floor.Draw();

            foreach (Support support in supportList)
            {
                support.Draw();
            }

            foreach (Obstacle obstacle in obstacleList)
            {
                obstacle.Draw();
            }

            toggle = !toggle;

            foreach (TrackPiece myModel in modelList)
            {
                myModel.Draw();
            }

            foreach (Ball ball in ParticleList)
            {
                ball.Draw();
            }

            foreach (PhysicsBlock block in BlockList)
            {
                block.Draw();
            }

            #region Debug Code (Display bounding box for all dynamic physics actors)
            //foreach (NxActor actor in physics.PhysicsScene.getActors())
            //{
            //    if (actor.isDynamic())
            //    {
            //        NxBounds3 worldBounds = actor.getFirstShape().getWorldBounds();
            //        worldBounds.fatten(-0.1f);
            //        Debug.DrawWorldBounds(worldBounds);
            //    }
            //}
            #endregion

            if (!levelComplete)
            {
                if (!toggle && !trackComplete)
                {
                    if (selectedTrackPiece != null)
                    {
                        selectedTrackPiece.Draw();
                    }
                    pointingArrow.Draw();
                }
                foreach (Ball ball in ballList)
                {
                    ball.Draw();
                }

                foreach (Ship ship in shipList)
                {
                    ship.Draw();
                }
                if (!trackComplete)
                {
                    foreach (Buttons button in buttons)
                    {
                        if (button.active)
                        {
                            button.Draw();
                        }
                    }
                    cameraDisc.Draw();
                }
                menu.Draw();
                //anything drawn after menu will be drawn on top
                spriteBatch.Begin();
                {
                    spriteBatch.Draw(
                        mouseCurser,
                        new Rectangle(
                        mouseStatePrevious.X - 16,
                        mouseStatePrevious.Y - 16,
                        32,
                        32),
                        Color.White);
                }
                spriteBatch.End();
            }

            graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;

            #region//draws selection track pieces
            //for debug
            //if (toggle)
            //{
            //    foreach (TrackPiece piece in selectionList)
            //    {
            //        piece.Draw();
            //    }
            //}
            //
            #endregion

            base.Draw(gameTime);
        }

        public static void Reset()
        {
            //release physics actors
            foreach (NxActor actor in physics.PhysicsScene.getActors())
            {
                Marshal.FreeHGlobal(actor.UserData);
                physics.PhysicsScene.releaseActor(actor);
            }

            //recreate the physics ground plane
            physics.CreateGround();
            
            InitializeStartingPieces(new Saver());
        }

        static public int pieceIndex = 1;//used to draw support every third piece
        public static bool SendToEndOfTrack(TrackPiece piece)
        {
            TrackPiece curr = LastTrackPiece;
            piece.modelRotation_ = curr.exitDirection_;
            piece.ModelPosition = curr.exitPoint_;
            piece.SetEntryExitPoints();
            if (piece.exitPoint_.Y <= 0 || modelList.Count>400)
            {
                return false;//if the trackpiece is going to be lower than ground level return false
            }

            //Disables collisions and places the trackpieces physics actor at the end of the track to check for collisions
            piece.Actor.FlagDisableCollision = true;
            piece.Update(); //updates the position of the physics actor to the position the model is drawn at

            bool collision = piece.CheckOverlap();
            //piece.Update();

            if (!collision)
            {
                if (!selectionList.Contains(piece))
                {
                    //this is where trackpieces are added to the linked arraylist
                    curr.Next_ = piece;
                    piece.Prev_ = curr;
                    lastTrackPiece = piece;
                    pointingArrow.Update();
                    if (pieceIndex % 5 == 0)
                    {
                        //adds support every 3rd track piece
                        piece.support = new Support(piece);
                        supportList.Add(piece.support);
                    }
                    pieceIndex++;
                }
            }
            return !collision; //returns true if there are no collsions
        }

        public void ExitGame()
        {
            physics.tickPhysics();
            physics.PhysicsSDK.release();
            menu.saver.Save();
            trackComplete = true;
            levelComplete = true;
            //if (menu.saver.Saved) { menu.saver.SynchroniseSave();}
            this.Exit();

        }

        //Remove a track piece and release its physics
        static public void RemoveTrackPiece(TrackPiece piece)
        {
            physics.PhysicsScene.releaseActor(piece.Actor);
            if (piece.support != null)
            {
                RemoveSupport(piece.support);
            }
            if (modelList.Contains(piece))
            {
                modelList.Remove(piece);
            }
            if (piece.Prev_ != null)
            {
                piece.Prev_.Next_ = null;
            }
            piece = null;
            pointingArrow.Update();
        }

        //Remove a support and release its physics
        static public void RemoveSupport(Support support)
        {
            //Null any existing references to the support
            if (support.TrackPiece.support != null)
            {
                support.TrackPiece.support = null;
            }
            if (support.TrackPiece != null)
            {
                support.TrackPiece = null;
            }
            supportList.Remove(support);
            physics.PhysicsScene.releaseActor(support.Actor);
        }

        //Remove a ball and release its physics
        static public void RemoveBall(Ball ball)
        {
            //delete pointer to avoid memory leak
            Marshal.FreeHGlobal(ball.Actor.UserData);

            physics.PhysicsScene.releaseActor(ball.Actor);
            ballList.Remove(ball);
        }

        public void CreateInterface()
        {
            menu = new Menu(graphics);
            menu.submenus[menu.submenus.Count-1].buttonHandler = new Buttons.buttonClicked(ExitGame);
            menu.submenus[1].buttonHandler = new Buttons.buttonClicked(menu.submenus[1].GoToBottom);
            buttons = new List<Buttons>();

            BUTTONOFFSET = graphics.GraphicsDevice.Viewport.Width - (ARROWBUTTONSPACING * 4 + BUTTONWIDTH * 3);

            #region Arrow Buttons
            /*
            buttons.Add(new Buttons(Content.Load<Texture2D>("Textures\\Button Texture\\LeftArrow"), new Rectangle(ARROWBUTTONSPACING, graphics.GraphicsDevice.Viewport.Height - BUTTONHEIGHT * 2 - ARROWBUTTONSPACING * 2, BUTTONWIDTH, BUTTONHEIGHT), new Buttons.buttonClicked(Buttons.LookLeft), new Buttons.mouseOver(Buttons.DoNothing)));
            buttons.Add(new Buttons(Content.Load<Texture2D>("Textures\\Button Texture\\RightArrow"), new Rectangle(ARROWBUTTONSPACING * 3 + BUTTONWIDTH*2, graphics.GraphicsDevice.Viewport.Height - BUTTONHEIGHT * 2 - ARROWBUTTONSPACING * 2, BUTTONWIDTH, BUTTONHEIGHT), new Buttons.buttonClicked(Buttons.LookRight), new Buttons.mouseOver(Buttons.DoNothing)));
            buttons.Add(new Buttons(Content.Load<Texture2D>("Textures\\Button Texture\\UpArrow"), new Rectangle(ARROWBUTTONSPACING * 2 + BUTTONWIDTH * 1, graphics.GraphicsDevice.Viewport.Height - BUTTONHEIGHT * 3 - ARROWBUTTONSPACING * 3, BUTTONWIDTH, BUTTONHEIGHT), new Buttons.buttonClicked(Buttons.LookUp), new Buttons.mouseOver(Buttons.DoNothing)));
            buttons.Add(new Buttons(Content.Load<Texture2D>("Textures\\Button Texture\\DownArrow"), new Rectangle(ARROWBUTTONSPACING * 2 + BUTTONWIDTH * 1, graphics.GraphicsDevice.Viewport.Height - BUTTONHEIGHT * 1 - ARROWBUTTONSPACING * 1, BUTTONWIDTH, BUTTONHEIGHT), new Buttons.buttonClicked(Buttons.LookDown), new Buttons.mouseOver(Buttons.DoNothing)));
            */
            
            #endregion

            straightModelButton = new Buttons(Content.Load<Texture2D>("Textures\\Button Texture\\StraightModelPic"), new Rectangle(BUTTONOFFSET + ARROWBUTTONSPACING * 2 + BUTTONWIDTH * 1, graphics.GraphicsDevice.Viewport.Height - BUTTONHEIGHT * 2 - ARROWBUTTONSPACING * 2, BUTTONWIDTH, BUTTONHEIGHT), new Buttons.buttonClicked(Buttons.CreateStraightPiece), new Buttons.mouseOver(Buttons.MouseOverStraightPiece));
            buttons.Add(straightModelButton);
            leftModelButton = new Buttons(Content.Load<Texture2D>("Textures\\Button Texture\\LeftModelPic"), new Rectangle(BUTTONOFFSET + ARROWBUTTONSPACING, graphics.GraphicsDevice.Viewport.Height - BUTTONHEIGHT * 2 - ARROWBUTTONSPACING * 2, BUTTONWIDTH, BUTTONHEIGHT), new Buttons.buttonClicked(Buttons.CreateLeftCornerPiece), new Buttons.mouseOver(Buttons.MouseOverLeftCornerPiece));
            buttons.Add(leftModelButton);
            rightModelButton = new Buttons(Content.Load<Texture2D>("Textures\\Button Texture\\RightModelPic"), new Rectangle(BUTTONOFFSET + ARROWBUTTONSPACING * 3 + BUTTONWIDTH * 2, graphics.GraphicsDevice.Viewport.Height - BUTTONHEIGHT * 2 - ARROWBUTTONSPACING * 2, BUTTONWIDTH, BUTTONHEIGHT), new Buttons.buttonClicked(Buttons.CreateRightCornerPiece), new Buttons.mouseOver(Buttons.MouseOverRightCornerPiece));
            buttons.Add(rightModelButton);
            upModelButton = new Buttons(Content.Load<Texture2D>("Textures\\Button Texture\\UpModelPic"), new Rectangle(BUTTONOFFSET + ARROWBUTTONSPACING * 2 + BUTTONWIDTH * 1, graphics.GraphicsDevice.Viewport.Height - BUTTONHEIGHT * 3 - ARROWBUTTONSPACING * 3, BUTTONWIDTH, BUTTONHEIGHT), new Buttons.buttonClicked(Buttons.CreateUpSlopePiece), new Buttons.mouseOver(Buttons.MouseOverUpSlopePiece));
            buttons.Add(upModelButton);
            downModelButton = new Buttons(Content.Load<Texture2D>("Textures\\Button Texture\\DownModelPic"), new Rectangle(BUTTONOFFSET + ARROWBUTTONSPACING * 2 + BUTTONWIDTH * 1, graphics.GraphicsDevice.Viewport.Height - BUTTONHEIGHT * 1 - ARROWBUTTONSPACING * 1, BUTTONWIDTH, BUTTONHEIGHT), new Buttons.buttonClicked(Buttons.CreateDownSlopePiece), new Buttons.mouseOver(Buttons.MouseOverDownSlopePiece));
            buttons.Add(downModelButton);
            undoButton = new Buttons(Content.Load<Texture2D>("Textures\\Button Texture\\Undo"), new Rectangle(BUTTONOFFSET + ARROWBUTTONSPACING * 1 + BUTTONWIDTH * 0, graphics.GraphicsDevice.Viewport.Height - BUTTONHEIGHT * 3 - ARROWBUTTONSPACING * 3, BUTTONWIDTH, BUTTONHEIGHT), new Buttons.buttonClicked(Buttons.Undo)); 
            buttons.Add(undoButton);
        }
        
        private void InitializeEndOfLevel()
        {
            //basicEffect.FogEnabled = false;
            cameraIsBall = false;
            ballList = new List<Ball>();
            shipList = new List<Ship>();
        }

        public static void CreateParticles(Vector3 position, Vector3 maxminvalues)
        {
            //ParticleList = new List<Ball>();
            foreach (Ball ball in ParticleList)
            {
                ball.Actor.FlagDisableGravity = true;
                ball.Position = position + new Vector3(
                    (float)(Ran.Next(-(int)(maxminvalues.X), (int)(maxminvalues.X))),
                    (float)(Ran.Next((int)(maxminvalues.Y), (int)(maxminvalues.Z))),
                    (float)(Ran.Next(-(int)(maxminvalues.X), (int)(maxminvalues.X)))
                    );
            }
            BallModel.LoadTexture(textureList[Ran.Next(0, textureList.Count)]);
            frameCount = 0;
        }

        public void EndOfLevel()//function to start end of level animations and load new level
        {
            nextCamPos.Y += SQUARESIZE;
            nextCamLook = nextCamPos + Vector3.UnitY * 1000000;
            //create fireworks stlye display using balls
            if (frameCount > 100)
            {
                CreateParticles(nextCamPos, new Vector3(100, 10000, 10100));
            }

            //load new level here
            if (campos.Y > TOTALGRIDSIZE * 50)
            {
                levelComplete = false;
                endOfLevelInitialized = false;
                trackComplete = false;
                //ParticleList = new List<MouseSquare>();
                frameCount = 0;
                Reset();
            }
        }

        public static void ButtonCheck()
        {
            downModelButton.active = SendToEndOfTrack(selectedDownSlopePiece);
            upModelButton.active = SendToEndOfTrack(selectedUpSlopePiece);
            leftModelButton.active = SendToEndOfTrack(selectedLeftCurvePiece);
            rightModelButton.active = SendToEndOfTrack(selectedRightCurvePiece);
            straightModelButton.active = SendToEndOfTrack(selectedStraightPiece);

            if (Vector3.Distance(lastTrackPiece.ModelPosition, StartingPiece.ModelPosition) > TOTALGRIDSIZE * 9)
            {
                foreach (Buttons button in buttons)
                {
                    if (button != undoButton)
                    {
                        button.active = false;
                    }
                }
            }
        }
    }
}
